﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;

namespace AlgorithmStarter
{
    /// <summary>
    /// classe che gestisce la connessione tra Corner e VirtualMachine
    /// </summary>
    public class CommunicationsManager
    {
        public string HostName { get; set; }
        public int Port { get; set; }

        /// <summary>
        /// indica se c'è connessione attiva
        /// </summary>
        public Boolean Connected
        {
            get
            {
                if (client == null)
                    return false;
                return client.Connected;
            }
        }
        
        public CommunicationsManager(string hostname, int port)
        {
            HostName = hostname;
            Port = port;
            streamIn = new MemoryStream(bufferIn, false);
            streamOut = new MemoryStream(bufferOut, true);
        }

        #region LatoVM

        TcpClient client;
        /// <summary>
        /// si connette all'host remoto.
        /// usata da VM verso Corner 
        /// (infatti la VM può assumere che il corner sia già aperto
        /// quindi ha senso che sia il corner a fare da server)
        /// </summary>
        public void ApriConnessione()
        {
            client = new TcpClient();
            client.BeginConnect(HostName, Port, new AsyncCallback(ApriConnessioneCallback), client);
        }

        private void ApriConnessioneCallback(IAsyncResult ares)
        {
            try
            {
                client.EndConnect(ares);
                if (client.Connected)
                {
                    stream = client.GetStream();
                    IniziaALeggere();
                    IniziaAScrivere();
                    if (ConnessioneStabilita != null)
                        ConnessioneStabilita();
                }
            }
            catch
            {
                Thread.Sleep(1000);
                ApriConnessione();
            }
        }
        #endregion

        #region latoCorner

        TcpListener listener;

        /// <summary>
        /// inizia ad accettare connessioni sulla porta Port.
        /// il Corner fa da server.
        /// </summary>
        public void MettitiInAscolto()
        {
            listener = new TcpListener(Port);
            listener.Start();
            listener.BeginAcceptTcpClient(new AsyncCallback(NuovaConnessioneCallback), listener);
        }

        private void NuovaConnessioneCallback(IAsyncResult ares)
        {
            client = listener.EndAcceptTcpClient(ares);
            stream = client.GetStream();
            IniziaALeggere();
            IniziaAScrivere();

            if (ConnessioneStabilita != null)
                ConnessioneStabilita();
        }




        #endregion

        #region funzioni comuni

        byte[] bufferIn = new byte[1024];
        byte[] bufferOut = new byte[1024];
        MemoryStream streamIn;
        MemoryStream streamOut;
        NetworkStream stream;
        bool PuoScrivere = true;

        Queue<Message> CodaMessaggiInUscita = new Queue<Message>();

        /// <summary>
        ///  invia un messaggio all'host remoto
        /// </summary>
        /// <param name="messaggio"></param>
        public void InviaMessaggio(Message messaggio)
        {
            // pulendo ogni volta la coda evito che uno avvii la stessa applicazione più volte
            // mentre si sta caricando.. ad esempio se uno fa doppio click su un bottone!
            CodaMessaggiInUscita.Clear();
            CodaMessaggiInUscita.Enqueue(messaggio);
            if (client != null && client.Connected && stream != null && stream.CanRead)
            {
                if (PuoScrivere)
                {
                    IniziaAScrivere();
                }
            }
        }

        private void IniziaAScrivere()
        {
            AddLog("inizia a scrivere()");
            try
            {
                if (CodaMessaggiInUscita.Count > 0)
                {
                    Message message = CodaMessaggiInUscita.Dequeue();
                    PuoScrivere = false;
                    IFormatter formatter = new BinaryFormatter();
                    streamOut.Seek(0, SeekOrigin.Begin);
                    formatter.Serialize(streamOut, message);
                    stream.BeginWrite(bufferOut, 0, bufferOut.Length, new AsyncCallback(ScriviCallback), client);
                }
            }
            catch (Exception ex)
            {
                AddLog(ex.ToString());
                InvocaConnessioneChiusa();
            }

        }

        private void ScriviCallback(IAsyncResult ares)
        {
            AddLog("ScriviCallback()");
            try
            {
                stream.EndWrite(ares);
                if (CodaMessaggiInUscita.Count == 0)
                    PuoScrivere = true;
                else
                    IniziaAScrivere();
            }
            catch (Exception ex)
            {
                AddLog(ex.ToString());
                InvocaConnessioneChiusa();
            }

        }

        private void IniziaALeggere()
        {
            AddLog("IniziaALeggere");
            try
            {
                if (client != null && client.Connected && stream != null && stream.CanRead)
                {
                    stream.BeginRead(bufferIn, 0, bufferIn.Length, new AsyncCallback(LeggiCallback), client);
                }
            }
            catch (Exception ex)
            {
                AddLog(ex.ToString());
                InvocaConnessioneChiusa();
            }
        }

        private void LeggiCallback(IAsyncResult ares)
        {
            AddLog("LeggiCallback");

            try
            {
                int quanti = stream.EndRead(ares);
                if (quanti == 0)
                {
                    InvocaConnessioneChiusa();
                    return;
                }
                IFormatter formatter = new BinaryFormatter();
                Message mex = (Message)formatter.Deserialize(streamIn);
                AddLog("LeggiCallback mex:" + mex.ToString());
                OnNuovoMessaggio(mex);
                streamIn.Seek(0, SeekOrigin.Begin);
                IniziaALeggere();
            }
            catch (Exception ex)
            {
                AddLog(ex.ToString());
                InvocaConnessioneChiusa();
            }

        }

        protected virtual void OnNuovoMessaggio(Message messaggio)
        {
            AddLog("OnNuovoMessaggio:" + messaggio.ToString());

            if (RicevutoMessaggio != null)
                RicevutoMessaggio(messaggio);
        }

        public delegate void MessaggioHandler(Message messaggio);
        /// <summary>
        /// invocato quanndo viene ricevuto un messaggio dall'host remoto
        /// </summary>
        public event MessaggioHandler RicevutoMessaggio;

        public delegate void ConnessioneHandler();
        /// <summary>
        /// evento sollevato quanndo si stabilisce la connessione con l'host remoto
        /// </summary>
        public event ConnessioneHandler ConnessioneStabilita;
        /// <summary>
        /// evento sollevato quanndo si stabilisce la connessione con l'host remoto
        /// </summary>
        public event ConnessioneHandler ConnessioneChiusa;

        private void InvocaConnessioneStabilita()
        {
            AddLog("InvocaConnessioneStabilita");
            if (ConnessioneStabilita != null)
                ConnessioneStabilita();
        }

        private void InvocaConnessioneChiusa()
        {
            AddLog("InvocaConnessioneChiusa");
            if (ConnessioneChiusa != null)
                ConnessioneChiusa();
        }

        #endregion

        #region LOG

        public void AddLog(string message)
        {
            if (false)
            {
                StreamWriter writer = File.AppendText(@"C:\AlgorithmStarter.txt");
                writer.WriteLine(DateTime.Now.ToLongTimeString() + ":" + message);
                writer.Flush();
                writer.Close();
            }
        }

        #endregion
    }

}
